/*
 * statistic_info.h
 *
 * Representación de información estadística sobre sobre uso del CPU y procesos
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

/* De aca sale la resolución del timer usado en /proc/stat: */
#include <asm/param.h> /* constante HZ */

#include "lexer.h"

#include "statistic_info.h"

/* TAD Auxiliar para parseo de /proc/stat */
typedef struct {
	Lexer *lexer;
	FILE *input;
	bstring tag;
	const char *tag_chars;
	bool last_ok;
	int last;
} StatLexer;

static StatLexer *stat_lexer_new (FILE *input, const char *tag_chars) {
	StatLexer *result = NULL;

	assert (input != NULL);
	result = calloc (sizeof (*result), 1);

	if (result == NULL) fclose (input);
	assert (result != NULL);
	result->input = input;
	result->lexer = lexer_new (input);
	result->tag = NULL;
	result->tag_chars = tag_chars; /* El llamador libera esto. */

	return result;
}

static void stat_lexer_destroy (StatLexer *l) {
	assert (l != NULL);
	lexer_destroy (l->lexer);
	bdestroy (l->tag);
	free (l);
}

static bool stat_lexer_is_off (const StatLexer *l) {
	assert (l!=NULL);

	return lexer_is_off (l->lexer);
}

static void stat_lexer_next_line (StatLexer *l) {
	assert (l!=NULL);
	assert (!stat_lexer_is_off (l));
	/* Saltear hasta siguiente linea, excepto la primera vez */
	if (lexer_is_started (l->lexer)) {
		lexer_skip_to (l->lexer, "\n");
		lexer_skip (l->lexer, "\n");
	}
	/* Leer etiqueta, saltear blancos posteriores */
	bdestroy (l->tag);
	lexer_next (l->lexer, l->tag_chars);
	if (!lexer_is_off (l->lexer)) {
		l->tag = lexer_item (l->lexer);
		lexer_skip (l->lexer, " \t");
	} else {
		l->tag = NULL;
	}
}

static void stat_lexer_skip_word (StatLexer *l) {
	assert (l!=NULL);
	
	lexer_skip (l->lexer, ALPHANUMERIC "-");
	lexer_skip (l->lexer, " \t");
}

static void stat_lexer_next_integer (StatLexer *l) {
	bstring number = NULL;
	assert (l!=NULL);
	
	l->last_ok = false;
	lexer_next (l->lexer, DIGIT);
	if (!lexer_is_off (l->lexer)) {
		number = lexer_item (l->lexer);
		l->last = atoi ((char *)(number->data));
		/* Son todos digitos, asi que se que puedo usar atoi. El unico caso
		 * de fallo posible es cuando `number' es "" */
		l->last_ok = blength (number) > 0;
		bdestroy (number);

		/* Consumir espacios*/
		lexer_skip (l->lexer, " \t");
	}
}

static bool stat_lexer_last_ok (const StatLexer *l) {
	assert (l!=NULL);

	return l->last_ok;
}

static int stat_lexer_last_integer (const StatLexer *l) {
	assert (l!=NULL);
	assert (stat_lexer_last_ok (l));

	return l->last;
}

static int stat_lexer_at (StatLexer *l, const char *tag) {
	assert (l!=NULL);
	assert (tag!=NULL);
	
	return l->tag != NULL && biseqcstr (l->tag, tag);
}

/* Recolección de estadísticas */
StatisticInfo statistic_info_new (const char *proc_dir) {
	StatisticInfo result;
	StatLexer *l = NULL;
	FILE *f = NULL;
	bstring path = NULL;

	assert (proc_dir != NULL);
	path = bfromcstr (proc_dir);

	/* Inicializar result */
	result.cpu_user = result.cpu_system = result.cpu_idle = 0L;
	result.context_switches = 0L;
	result.process_count = 0L;
	result.boot_date = (time_t) 0L;
	result.disk_requests = 0L;
	result.memory_available = 0L;
	result.memory_total = 0L;
	result.load_average_1 = 0L;
	
	/* Parsear /proc/stat */
	bcatcstr (path, "/stat");
	f = fopen ((char *)path->data, "r");
	if (f != NULL) {
		l = stat_lexer_new (f, ALPHANUMERIC);
		assert (l != NULL);
		
		while (!stat_lexer_is_off (l)) {
			stat_lexer_next_line (l);
			if (stat_lexer_at (l, "cpu")) {
				stat_lexer_next_integer (l);
				result.cpu_user = stat_lexer_last_integer (l) * (1000 / HZ);
				stat_lexer_next_integer (l);
				result.cpu_user += stat_lexer_last_integer (l) * (1000 / HZ);
				stat_lexer_next_integer (l);
				result.cpu_system = stat_lexer_last_integer (l) * (1000 / HZ);
				stat_lexer_next_integer (l);
				result.cpu_idle = stat_lexer_last_integer (l) * (1000 / HZ);
			} else if (stat_lexer_at (l, "ctxt")) {
				stat_lexer_next_integer (l);
				result.context_switches = stat_lexer_last_integer (l);
				assert(0<result.context_switches);
			} else if (stat_lexer_at (l, "processes")) {
				stat_lexer_next_integer (l);
				result.process_count = stat_lexer_last_integer (l);
				assert(0<result.process_count);
			} else if (stat_lexer_at (l, "btime")) {
				stat_lexer_next_integer (l);
				result.boot_date = stat_lexer_last_integer (l);
			}
		}
	
		stat_lexer_destroy (l);
		fclose(f); /*agregado por mi */
	}

	/* Parsear /proc/diskstats */
	bassigncstr (path, proc_dir);
	bcatcstr (path, "/diskstats");
	f = fopen ((char *)path->data, "r");
	if (f != NULL) {
		l = stat_lexer_new (f, DIGIT " ");
		assert (l != NULL);

		stat_lexer_next_line (l);
		stat_lexer_skip_word (l);
		while (!stat_lexer_is_off (l)) {
			int rq;
			stat_lexer_next_integer (l);
			assert (stat_lexer_last_ok (l)); /* Suponemos, por formato de entrada */
			rq = stat_lexer_last_integer (l); /* Cantidad de lecturas, campo #1*/
			stat_lexer_next_integer (l); /* #2 */
			stat_lexer_next_integer (l); /* #3 */
			stat_lexer_next_integer (l); /* #4 */
			/* Distinguir entre estadísticas de 4 y de 11 elementos, que vienen
			 * mezcladas... ver /usr/src/linux/Documentation/iostats.txt
			 */
			stat_lexer_next_integer (l); /* #5? */
			if (stat_lexer_last_ok (l)) {
				rq += stat_lexer_last_integer (l);
				/* Formato de 11 elementos... */
				result.disk_requests += rq;
			}
			stat_lexer_next_line (l);
			stat_lexer_skip_word (l);
		}
		
		stat_lexer_destroy (l);
		fclose  (f); /*agregado por mi */
	}
	
	/* parseo meminfo */
	bassigncstr (path, proc_dir);
	bcatcstr (path, "/meminfo");
	f = fopen ((char *)path->data, "r");
	if (f!=NULL)
	{
		fscanf(f, "MemTotal: %ld kB\n", &result.memory_total);
		fscanf(f, "MemFree: %ld kB\n", &result.memory_available);
		fclose(f);
	}

	/* Parsear loadavg. El parseo es muy simple, no usamos un Lexer sino scanf */
	bassigncstr (path, proc_dir);
	bcatcstr (path, "/loadavg");
	f = fopen ((char *)path->data, "r");
	if (f != NULL) {
		fscanf (f, "%f", &result.load_average_1);
		fclose (f);
	}

	bdestroy (path);
	
	return result;
}

void statistic_info_destroy (StatisticInfo self) {
	/* No hay memoria alojada, no hay nada para hacer */
}
